package com.song.uaa.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;

import com.song.uaa.core.config.SongProperties;
import com.song.uaa.gateway.security.AudienceValidator;
import com.song.uaa.gateway.security.SecurityUtil;
import com.song.uaa.gateway.web.filter.CookieCsrfFilter;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;

import java.util.HashSet;
import java.util.Set;

import static org.springframework.security.config.Customizer.withDefaults;

import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.SUB;

/**
 * 安全配置
 */
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
	
	
	private final SongProperties songProperties;
	
	public SecurityConfig(SongProperties songProperties) {
		this.songProperties = songProperties;
	}
	
	/**
	 * 授权链
	 * @param http
	 * @return
	 */
	@Bean
	SecurityWebFilterChain authorizationSecurityFilterChain(ServerHttpSecurity http) {
		http
			.securityMatcher(
					new NegatedServerWebExchangeMatcher(new OrServerWebExchangeMatcher(pathMatchers("/assets/**")))
			)
			.cors(withDefaults())
//			.csrf(withDefaults())
			.csrf(csrf ->
		            csrf
		                .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
		                // See https://stackoverflow.com/q/74447118/65681
		                .csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
			)
			// See https://github.com/spring-projects/spring-security/issues/5766
            .addFilterAt(new CookieCsrfFilter(), SecurityWebFiltersOrder.REACTOR_CONTEXT)
			.authorizeExchange(authz ->
				authz
					.pathMatchers("/").permitAll()
					.pathMatchers("/*.*").permitAll()
	                .pathMatchers("/api/**").authenticated()
	                .pathMatchers("/services/**").authenticated()
			)
			.oauth2Login(withDefaults())
			.oauth2Client(withDefaults())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
		return http.build();
	}
	
	/**
	 * 解析令牌声明中的角色权限信息。
	 * 作用授权过程中获取到访问令牌时，在用户端点拉取用户信息
	 */
	@Bean
	ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
		final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
		return userRequest -> {
			return delegate
					.loadUser(userRequest)
					.map(user -> {
						Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
						// 客戶端通过用户端点获得声明后，会将IdToken及相关的声明包装为OidcUserAuthority
						// @see: org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService.getUser(OidcUserRequest, OidcUserInfo)
						user
							.getAuthorities()
							.forEach(authority -> {
								if (authority instanceof OidcUserAuthority) {
	                                OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
	                                mappedAuthorities.addAll(
	                                	SecurityUtil.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims())
	                                );
	                            }
							});
						return new DefaultOidcUser(mappedAuthorities, user.getIdToken(), SUB);
					});
		};
	}
	
	/**
	 * 从Jwt令牌中抽取出用户权限
	 * @return 转换器
	 */
	private Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() {
		ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
		jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
            new Converter<Jwt, Flux<GrantedAuthority>>() {
                @Override
                public Flux<GrantedAuthority> convert(Jwt jwt) {
                    return Flux.fromIterable(SecurityUtil.extractAuthorityFromClaims(jwt.getClaims()));
                }
            }
        );
        jwtAuthenticationConverter.setPrincipalClaimName(SUB);
        return jwtAuthenticationConverter;
	}
	
	/**
	 * 解析JWT令牌： JwtDecoder
	 */
	@Bean
    ReactiveJwtDecoder jwtDecoder(ReactiveClientRegistrationRepository registrations) {
        Mono<ClientRegistration> clientRegistration = registrations.findByRegistrationId("oidc");

        return clientRegistration
            .map(oidc ->
                createJwtDecoder(
                    oidc.getProviderDetails().getIssuerUri(),
                    oidc.getProviderDetails().getJwkSetUri(),
                    oidc.getProviderDetails().getUserInfoEndpoint().getUri()
                )
            )
            .block();
    }
	
	private ReactiveJwtDecoder createJwtDecoder(String issuerUri, String jwkSetUri, String userInfoUri) {
		NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(jwkSetUri);
        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(songProperties.getSecurity().getOauth2().getAudience());
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);
        return new ReactiveJwtDecoder() {
			
			@Override
			public Mono<Jwt> decode(String token) throws JwtException {
				return jwtDecoder.decode(token).flatMap(jwt -> enrich(token, jwt));
			}
			
			private Mono<Jwt> enrich(String token, Jwt jwt) {
				//TODO: 从授权服务器获取用户信息，并编码到jwt中
				return Mono.just(jwt);
			}
		};
	}
}
